package gomz.server;

import java.net.Socket;

import gomz.res.Category;
import gomz.res.Hexagram;
import gomz.res.HexagramCore;
import gomz.res.Message;
import gomz.res.Question;
import gomz.res.QuestionCore;
import gomz.res.User;
import gomz.res.UserCore;
import java.util.ArrayList;

/**
 * Diese Klasse wird verwendet um vom Server aus mit dem Client zu Kommunizieren. Sobald der Serversocket
 * ein Socket mit der Verbindung zu Client erstellt hat, laeuft diese Klasse in einem eigenen Thread
 * @author Schatzmann
 * @author Murillo
 */
public class ServerToClientCon extends Thread{
// ********************************************************************************
// Variablen
// ********************************************************************************
	private Socket client;
	private ThreadFinishedListener threadListener;
        //Verbindung zur Datenbank
        private DBConnection dbConnection;
        private ArrayList<LogListener> logListeners = new ArrayList<LogListener>();
	
// ********************************************************************************
// Konstruktor
// ********************************************************************************
	public ServerToClientCon(Socket client, ThreadFinishedListener threadListener, String dbUser, String dbPW, ArrayList<LogListener> logListeners) {
		this.client = client;
		this.threadListener = threadListener;
                this.logListeners = logListeners;
        this.dbConnection = new DBConnection(dbUser, dbPW);
	}

// ********************************************************************************
// Thread
// ********************************************************************************
	public void run(){
            //neuer Client ins Log schreiben
	    writeLog("Request from client " + client.getInetAddress().toString() + " for server (me) " + client.getLocalAddress().toString() + "\n");
            //Message auslesen
	    Message.ObjectTyp MsgInObjTyp = null;
	    Message.ActionTyp MsgInActionTyp = null;  
            //Solange kein Logout kommt laeuft der Thread
	    while (MsgInActionTyp != Message.ActionTyp.Logout) {
	        // Read a message from the client
	        Message msgIn = null;	        
	        try{
                        //warte auf eine Nachricht
	        	msgIn = Message.receive(client);
	        }
	        catch(Exception e){
                        //Falls verbindung abbricht oder beendet wird
	        	writeLog("eine Client Verbindung wurde getrennt");
	        	break;
	        }

	        if(msgIn != null){
		        MsgInActionTyp = msgIn.getActionTyp();
		        MsgInObjTyp = msgIn.getObjectTyp();
		
		
                        //lese die Nachricht und loese dann die richtige Methode aus
		        switch(MsgInObjTyp){
	                case User:
	                    switch (MsgInActionTyp){
	                    	case Save:
	                    		saveUser(msgIn);
	                    		break;
	                        case Logout:
	                            this.userLogout(msgIn); 
	                            break;
	                        case Delete:
	                        	delUser(msgIn);
	                        	break;
	                        case Get:
	                        	getUser(msgIn);
	                        	break;
	                        default:
	                            this.failMessage(msgIn);
	                            break;
	                    }
	                    break;
	                case UserCore:
	        			switch (MsgInActionTyp){
	                        case Login:
	                      this.userCoreLogin(msgIn);
	                            break;
	                        case GetAll:
	                        	getUserCoresAll(msgIn);   
	                            break;
	                        default:
	                            this.failMessage(msgIn);
	                            break;         
	                    }
	                    break;
	                case Hexagram:
	                    switch(MsgInActionTyp){
                            default:
                                    this.failMessage(msgIn);
                                    break;
                            case Save:
                                    saveHexagram(msgIn, false);
                                    break;
                            case SaveAll:
                            	 saveHexagram(msgIn, true);
                                 break;
                            case Get:
                                getHexagram(msgIn);
                                break;
	            		}
	            		break;
	                case HexagramCore:
	                    switch (MsgInActionTyp){
	                        case GetAll:
	                        	getHexagramCoresAll(msgIn);
	                            break;
	                    }
	                    break;
	                case Category:
	                    switch (MsgInActionTyp){
	                        default:
	                            this.failMessage(msgIn);
	                            break;
	                        case GetAll:
	                            getCategories(msgIn);
	                            break;
	                        case Save:
	                        	saveCat(msgIn);
	                        	break;
	                        case Delete:
	                        	delCat(msgIn);
	                        	break;
	                    }
	                    break;
	                case Question:
		            	switch (MsgInActionTyp){
		            		default:
		            			this.failMessage(msgIn);
		            			break;
		            		case Save:
		            			saveQuestion(msgIn);
		            			break;
		            		case Delete:
		            			delQuestion(msgIn);
		            			break;
		            		case Get:
		            			getQuestion(msgIn);
		            			break;
		            	}
		                break;
	                case QuestionCore:
		            	switch (MsgInActionTyp){
		            		default:
		            			this.failMessage(msgIn);
		            			break;
		            		case GetAll:
		            			getQuestionCoresAll(msgIn);
		            			break;
		            	}
		                break;
	                default:
	                    this.failMessage(msgIn);
	                    break;
		        }
	        }
	    }

            writeLog("Ein User hat sich ausgeloggt - beende den Thread");
            dbConnection.closeCon();
	    threadListener.notifyConnectionFinished(this);
	}
//***********************************************************************
//MEHTODEN
//***********************************************************************
        /**
         * schreibe etwas ins Log
         * @param logString String welcher dem Log hinzugefuegt werden soll
         */
        public void writeLog(String logString){
    	for (LogListener ll : logListeners){
    		ll.doNotifyTxtDataChange(logString);
    	}
        }
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag die uebergebene Kategorie zu loeschen und sendet dem Client
	 * eine Bestaetigung mit dem Inhalt ob der Vorgang durchgefuehrt werden konnte.
	 * @param msgIn vom Client erhaltene Message mit einer zu loeschenden Kategorie
	 */
	private synchronized void delCat(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());
		boolean re = false;
		if(msgIn.getCategoryCollection().size()>0){
			re = dbConnection.catDelete(msgIn.getCategoryCollection().get(0).getId());
		}
		if (re){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setObjectTyp(Message.ObjectTyp.Category);
		}
		try {
			msgOut.send(client);
		} catch (Exception e) {
			 writeLog("Error: " + e);
		}
	}
		
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag die uebergebene Kategorie zu speichern und sendet diese dem Client
	 * mit der ID zurueck. Falls der Vorgang nicht durchgefuehrt werden, konnte wird eine "Fail" Message zurueckgegeben.
	 * @param msgIn vom Client erhaltene Message mit der zu speichernden Kategorie
	 */
	private synchronized void saveCat(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());
		Category reCat = null;
		if(msgIn.getCategoryCollection().size()>0){
			reCat = dbConnection.catSave(msgIn.getCategoryCollection().get(0));
		}
		if (reCat!= null){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setObjectTyp(Message.ObjectTyp.Category);
			ArrayList<Category> reCatCol = new ArrayList<Category>();
			reCatCol.add(reCat);
			msgOut.setCategoryCollection(reCatCol);
		}
		else{
			msgOut = new Message(Message.ActionTyp.Fail);
			msgOut.setCategoryCollection(new ArrayList<Category>());
		}
		try {
			msgOut.send(client);
		} catch (Exception e) {
			 writeLog("Error: " + e);
		}
	}
		
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag das Hexagramm der uebergebene ID zu hohlen und sendet dieses dem Client.
	 * Falls das Hexagramm nocht gehohlt werden konnte, wird eine "Fail" Message zurueckgesendet.
	 * @param msgIn vom Client erhaltenen Message mit der ID des zu hohlenden Hexagramms
	 */
	private synchronized void getHexagram(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());

		Hexagram reHexagram = dbConnection.hexagramGet(msgIn.getHexagram().getId(), true);
		if (reHexagram != null){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setHexagram(reHexagram);                                				
		}
		
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
		
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag den uebergebene Benutzer zu speichern und sendet diesen dem Client
	 * mit einer ID zurueck. Falls der Vorgang nicht durchgefuehrt werden konnte, wird eine "Fail" Message zurueckgegeben
	 * @param msgIn vom Client erhaltene Message mit dem zu speichernden Benutzer
	 */
	private synchronized void saveUser(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());

		User reUser = dbConnection.userSave(msgIn.getUser());
		if (reUser != null){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setUser(reUser);                                				
		}
		
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
	
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag den Benutzer der uebergebene ID zu hohlen und sendet diese dem Client.
	 * Falls der Vorgang nicht durchgefuehrt werden konnte wird eine "Fail" Message zurueckgesendet.
	 * @param msgIn vom Client erhaltene Message mit der ID des zu hohlenden Benutzer
	 */
	private synchronized void getUser(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());
		User re = dbConnection.userGet(msgIn.getUser().getId());
		
		if (re != null){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setUser(re);
		}
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
	
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag den uebergebene Benutzer zu loeschen und sendet dem Client
	 * eine Bestaetigung mit dem Inhalt ob der Vorgang durchgefuehrt werden konnte.
	 * @param msgIn vom Client erhaltene Message mit dem zu loeschenden Benutzer
	 */
	private synchronized void delUser(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());
		boolean re = dbConnection.userDelete(msgIn.getUser().getId());
		if (re){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setObjectTyp(Message.ObjectTyp.User);
		}
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
	
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag alle QuestionCores dem Benutzer zugaenglichen QuestionCores
	 * zu hohlen und sendet diese dem Client zurueck. Falls der Vorgang nicht durchgefuehrt werden konnte, wird eine 
	 * "Fail" Message zurueckgesendet.
	 * @param msgIn vom Client erhaltene Message
	 */
	private synchronized void getQuestionCoresAll(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());

		ArrayList<QuestionCore> reQuestionCoreArr = dbConnection.questionGettAll();
		if (reQuestionCoreArr != null){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setQuestionCoreCollection(reQuestionCoreArr);                                				
		}
		
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
		
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag alle HexagramCores zu hohlen und sendete diese dem Client zurueck.
	 * Falls der Vorgang nicht durchgefuehrt werden konnte, wird eine "Fail" Message zurueckgesendet.
	 * @param msgIn vom Client erhaltene Message
	 */
	private synchronized void getHexagramCoresAll(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());

		ArrayList<HexagramCore> reHexCoreArr = dbConnection.hexGetAll();
		if (reHexCoreArr != null){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setHexagramCoreCollection(reHexCoreArr);                                				
		}
		
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
	
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag alle UserCores zu hohle und sendet sie dem Client.
	 * Falls der Vorgang nicht durchgefuehrt werden konnte, wird eine "Fail" Message zurueckgesendet.
	 * @param msgIn vom Client erhaltene Message
	 */
	private synchronized void getUserCoresAll(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());

		ArrayList<UserCore> reUserArr = dbConnection.userGetAll();
		if (reUserArr != null){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setUserCoreCollection(reUserArr);                                				
		}
		
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
		
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag alle Kategoriewn zu hohle und sendet sie dem Client.
	 * Falls der Vorgang nicht durchgefuehrt werden konnte, wird eine "Fail" Message zurueckgesendet. 
	 * @param msgIn vom Client erhaltene Message
	 */
	private synchronized void getCategories(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());

		ArrayList<Category> reCatArr = dbConnection.catGetAll();
		if (reCatArr != null){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setCategoryCollection(reCatArr);                                				
		}
		
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
	
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag das uebergebene Hexagram zu speichern und sendet dem Client
	 * eine Bestaetigung mit dem Inhalt ob der Vorgang durchgefuehrt werden konnte.
	 * @param msgIn vom Client erhaltene Message mit dem zu speichernden Hexagram
	 * @param boolean Boolean ob der Originaltext von den Hexagrammen auch ueberschrieben werden soll.
	 */
	private synchronized void saveHexagram(Message msgIn, boolean saveOriTxt){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());

		boolean re = dbConnection.hexagramSave(msgIn.getHexagram(), saveOriTxt);
		if (re){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setObjectTyp(msgIn.getObjectTyp());
		}
		
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
		
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag die Fragestellung zu hohle, deren ID uebergeben wird und sendet sie dem Client.
	 * Falls der Vorgang nicht durchgefuehrt werden konnte, wird eine "Fail" Message zurueckgesendet.
	 * @param msgIn vom Clien erhaltene Message mit der ID der zu hohlenden Fragestellung
	 */
	private synchronized void getQuestion(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());
		
		Question reQuestion = dbConnection.questionGet(msgIn.getQuestion().getId());
		if (reQuestion != null){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setQuestion(reQuestion);                                				
		}
		
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
	
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag die Fragestellung, deren ID uebergeben wird, zu loeschen und 
	 * sendet dem Client eine Bestaetigung mit dem Inhalt ob der Vorgang durchgefuehrt werden konnte.
	 * @param msgIn com Client erhaltene Message mit der ID der zu loeschenden Fragestellung
	 */
	private synchronized void delQuestion(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());
		
		boolean re = dbConnection.questionDelete(msgIn.getQuestion().getId());
		if (re){
			msgOut = new Message(Message.ActionTyp.Success);
			msgOut.setObjectTyp(msgIn.getObjectTyp());
		}
		
		try {
			msgOut.send(client);
		} catch (Exception e) {
                        writeLog("Error: " + e);
		}
	}
	
	//Author Murillo
	/**
	 * Diese Methode gibt der DBConnection den Auftrag die uebergebene Fragestellung zu speichern und sendet sie dem Client
	 * mit einer ID zurueck. Falls der Vorgang nicht durchgefuehrt werden konnte, wird eine "Fail" Message zurueckgesendet.
	 * @param msgIn vom Client erhaltene Message mit der zu speichernden Fragestellung
	 */
	private synchronized void saveQuestion(Message msgIn){
		Message msgOut = new Message(Message.ActionTyp.Fail);
		msgOut.setObjectTyp(msgIn.getObjectTyp());
		
		if (dbConnection != null){
			Question re = null;
			
			int qId = dbConnection.questionSave(msgIn.getQuestion());
			if (qId != 0){
				re  = dbConnection.questionGet(qId);
				if (re != null){
					msgOut = new Message(Message.ActionTyp.Success);
					msgOut.setQuestion(re);
				}
			}

			try {
				msgOut.send(client);
			} catch (Exception e) {
                                writeLog("Error: " + e);
			}
		}
	}
    /**
     * Author: schatzmann
     * Falls eine Anfrage nicht verarbeitet werden konnte
     * anworte dem Client mit einem ActionTyp.Fail
     * @param msgIn nachricht welche empfangen wurde
     */
    public synchronized void failMessage(Message msgIn){
        Message msgOut = new Message(Message.ActionTyp.Fail);
        msgOut.setObjectTyp(msgIn.getObjectTyp());
                    try {
        msgOut.send(client);
        } 
        catch (Exception e) {
            writeLog("Error: " + e);
        }
    }
    /**
     * Author Schatzmann
     * Einlogen eines Users
     * Zuruecksenden des gesammten Users wenn erfolgreich
     * @param msgIn nachricht welche empfangen wurde
     */    
    public synchronized void userCoreLogin(Message msgIn){
        //Variable msgOut: Antwort
        Message msgOut = null;
        //Lesen des UserCores aus der msgIn
        UserCore userCore = msgIn.getUserCoreCollection().get(0);

        //DB abfragen ob Login erfolgreich
        boolean dbAnswerLogin = dbConnection.userLogin(userCore); 
        if(dbAnswerLogin){
            //falls DBLogin erfolgreich, frage UserID ab
            //getUserId setzt die ID automatisch in den übergebenen UserCore
            boolean dbAnswerUserId = dbConnection.getUserId(userCore);
            if(dbAnswerUserId){
                //falls DBLogin und DBgetUserId beide positiv waren
                User dbUser = dbConnection.userGet(userCore.getId());
                //speichere Active User
                dbConnection.setActiveUser(dbUser);
                //positive Rückmeldung an Client senden mit dem User damit der Client diesen in den GlobalRes speichern kann
                msgOut = new Message(Message.ActionTyp.Success);
                msgOut.setUser(dbUser);
                //msgOut senden
                try {
                    msgOut.send(client);
                } 
                catch (Exception e) {
                    writeLog("Error: " + e);
                }
            }
            else{
                //konnte zwar einloggen aber keine ID finden
                this.failMessage(msgIn);
            }
        }
        else{
            //login fehlgeschlagen
            this.failMessage(msgIn);
        }

    }

    /**
     * Author Schatzmann
     * Methode um einen User auszulogen
     * @param msgIn Nachricht welche empfangen wurde
     * wird nicht verwendet
     */
    private synchronized void userLogout(Message msgIn) {
            //Variable msgOut: Antwort
            //positive Rückmeldung an Client senden mit dem User damit der Client diesen in den GlobalRes speichern kann
            Message msgOut = new Message(Message.ActionTyp.Success);
            //Ausgelogten UserCore mit Nachricht mitsenden
            ArrayList<UserCore> arrayList = new ArrayList<UserCore>();
            arrayList.add(this.dbConnection.getActiveUser());
            
            msgOut.setUserCoreCollection(arrayList);
            //msgOut senden
            try {
                msgOut.send(client);
            } 
            catch (Exception e) {
                    writeLog("Error: " + e);
            }
    }
// ********************************************************************************
// Getter Setter
// ********************************************************************************
    public ThreadFinishedListener getThreadListener() {
        return threadListener;
    }

    public void setThreadListener(ThreadFinishedListener threadListener) {
        this.threadListener = threadListener;
    }

    public Socket getClient() {
        return client;
    }

    public void setClient(Socket client) {
        this.client = client;
    }
    
    
        
}
